package com.hsj.bean;

import com.hsj.anno.Bean;
import com.hsj.anno.Di;
import com.hsj.dao.UserDao;
import com.hsj.service.UserService;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;


/**
 *    手写IOC的过程
 *    第一步： 创建模块
 *    第二步： 创建测试类
 *    第三步： 创建两个注解，@Bean创建对象，@Di属性注入
 *    第四步： 创建Bean容器接口，ApplicationContext定义方法，返回对象
 *    第五步： 实现Bean容器接口，返回对象，根据包规则加载Bean
 *              例如 包com.hsj  扫描com.hsj这个包，看类上面是否哟u@Bean注解，如果有就把该类通过反射实例化
 *   手写IOC
 *   通过注解@Bean 将对象通过反射创建并注入到spring容器中
 *   通过注解@Di， 获取容器所有对象并将属性通过反射赋值，
 *
 */
public class AnnotationApplicationContext implements ApplicationContext {

    //创建map集合， 用于存放Bean对象
    private Map<Class, Object> beanFactory = new HashMap<>();
    private String rootPath = "";

    @Override
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);
    }

    //设置扫描包规则
    //当前包及子包， 哪个类有@Bean注解， 把这个类通过反射实例化
    public AnnotationApplicationContext(String basePackage) {
        //basePackage传入的参数是 com.hsj  ，
        //1. 把. 换成\ ，因为要扫描包路径下面的文件，需要获取到绝对路径
        String packagePath = basePackage.replaceAll("\\.", "\\/");

        //2. 获取包的绝对路径
        try {
            Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                String filePath = URLDecoder.decode(url.getFile(), "utf-8"); //这一步是为了将 路径符号/ 由%5 转译符 转译成正常的 /路径符号
                //获取包前面路径的部分， 字符串截取
                rootPath = filePath.substring(0, filePath.length() - packagePath.length());
                // 3.扫描包
                loadBean(new File(filePath));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        //4.属性注入
        loadDi();
    }
//属性注入
    private void loadDi() {
        //实例化对象在beanFactory的map集合里面
        //1.遍历beanFactory的map集合
        Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
        for(Map.Entry<Class,Object> entry:entries){
            //2.获取map集合每个对象的属性
              //2.1遍历获得map中的每个对象
            Object obj = entry.getValue();
              //2.2获取到对象的类型
            Class<?> clazz = obj.getClass();
              //2.3反射获取所有属性
            Field[] fields = clazz.getDeclaredFields();
            //3.遍历得到的每个对象的属性数组，得到属性
            for(Field field:fields){
                //4.判断属性上是否有@Di注解
                Di annotation = field.getAnnotation(Di.class);
                if(annotation !=null){
                    // 如果是私有属性，需要设置
                     field.setAccessible(true);
                    try {
                        //5.如果有@Di注解，把对象进行设置（注入）
                        field.set(obj,beanFactory.get(field.getType()));
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }

    }

    //包扫描的过程，用反射创建对象放入beanFactory
    private void loadBean(File file) throws Exception {
        //1. 判断当前是否是文件夹
        if (file.isDirectory()) {
            //2. 获取文件夹里面所有内容
            File[] childrenFiles = file.listFiles();
            //3. 判断文件是否为空，如果为空直接返回
            if (childrenFiles == null || childrenFiles.length == 0) {
                return;
            }
            //4. 如果文件夹里面不为空，遍历文件夹所有内容
            for (File child : childrenFiles) {
                //4.1 遍历得到每个File对象，继续判断是否为文件，如果不是文件 还是文件夹，递归，直接里面没有内容为止
                if (child.isDirectory()) {
                    //递归
                    loadBean(child);
                } else {
                    //4.2 遍历得到File对象不是文件夹，而是文件
                    //4.3 得到包路径+类名称部分, 例如 com.hsj.bean.AnnotationApplicationContext;
                    String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1);
                    //4.4 判断当前文件类型是否是 .class，
                    if (pathWithClass.contains(".class")) {
                        //4.5 如果是.class类型，把路径\替换成.  把.class去掉  例如 com.hsj.service.UserServiceImpl
                        String allName = pathWithClass.replaceAll("\\/", "\\.").replace(".class", "").substring(1);
                        //4.6 判断类上面是否有@Bean注解， 如果有注解 就使用反射实例化
                        //4.6.1获取类的class对象
                        Class clazz = Class.forName(allName);
                        //4.6.2判断是否是接口
                        if (!clazz.isInterface()) {
                            //4.6.3 判断类上面是否有@Bean注解
                            Annotation annotation =  clazz.getAnnotation(Bean.class);
                            if (annotation != null) {
                                //4.6.4实例化
                                Object instance = clazz.getConstructor().newInstance();
                                //4.7 把对象实例化之后，放到Map集合beanFactory中
                                if (clazz.getInterfaces().length > 0) {
                                    //4.7.1 如果当前类有接口，让接口 作为map的key
                                    beanFactory.put(clazz.getInterfaces()[0], instance);
                                } else {
                                    ////4.7.2 如果当前类没接口，让class作为map的key
                                    beanFactory.put(clazz, instance);
                                }
                            }
                        }
                    }
                }


            }

        }

    }

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationApplicationContext("com.hsj");
        UserService userService  = (UserService) applicationContext.getBean(UserService.class);
        UserDao userDao = (UserDao) applicationContext.getBean(UserDao.class);
        System.out.println(userService);
        userService.add();
    }

}
